﻿using System;
using System.Diagnostics.Contracts;
using System.Reactive.Linq;
using System.Reactive.Subjects;

namespace Microsoft.Phone.UserData
{
	/// <summary>
	/// Provides <see langword="static"/> extension methods for the <see cref="Appointments"/> class.
	/// </summary>
	public static class AppointmentsExtensions
	{
		/// <summary>
		/// Asynchronously searches for appointments that occur between the specified start date and time and end date and time.
		/// </summary>
		/// <param name="appointments">The object used to acquire appointment data.</param>
		/// <param name="startTimeInclusive">The start date and time to use to search for appointments.</param>
		/// <param name="endTimeInclusive">The end date and time to use to search for appointments.</param>
		/// <returns>A singleton observable sequence containing the search results.</returns>
		public static IObservable<AppointmentsSearchEventArgs> SearchObservable(this Appointments appointments, DateTime startTimeInclusive, DateTime endTimeInclusive)
		{
			Contract.Requires(appointments != null);
			Contract.Ensures(Contract.Result<IObservable<AppointmentsSearchEventArgs>>() != null);

			return SearchObservable(appointments, token => appointments.SearchAsync(startTimeInclusive, endTimeInclusive, token));
		}

		/// <summary>
		/// Asynchronously searches for appointments that occur between the specified start date and time and end date and time, 
		/// from the specified data source.
		/// </summary>
		/// <param name="appointments">The object used to acquire appointment data.</param>
		/// <param name="startTimeInclusive">The start date and time to use to search for appointments.</param>
		/// <param name="endTimeInclusive">The end date and time to use to search for appointments.</param>
		/// <param name="account">The data source to search for appointments.</param>
		/// <returns>A singleton observable sequence containing the search results.</returns>
		public static IObservable<AppointmentsSearchEventArgs> SearchObservable(this Appointments appointments, DateTime startTimeInclusive, DateTime endTimeInclusive, Account account)
		{
			Contract.Requires(appointments != null);
			Contract.Ensures(Contract.Result<IObservable<AppointmentsSearchEventArgs>>() != null);

			return SearchObservable(appointments, token => appointments.SearchAsync(startTimeInclusive, endTimeInclusive, account, token));
		}

		/// <summary>
		/// Asynchronously searches for appointments that occur between the specified start date and time and end date and time, 
		/// returning no more than the specified number of appointments.
		/// </summary>
		/// <param name="appointments">The object used to acquire appointment data.</param>
		/// <param name="startTimeInclusive">The start date and time to use to search for appointments.</param>
		/// <param name="endTimeInclusive">The end date and time to use to search for appointments.</param>
		/// <param name="maximumItems">The maximum number of appointments to return.</param>
		/// <returns>A singleton observable sequence containing the search results.</returns>
		public static IObservable<AppointmentsSearchEventArgs> SearchObservable(this Appointments appointments, DateTime startTimeInclusive, DateTime endTimeInclusive, int maximumItems)
		{
			Contract.Requires(appointments != null);
			Contract.Ensures(Contract.Result<IObservable<AppointmentsSearchEventArgs>>() != null);

			return SearchObservable(appointments, token => appointments.SearchAsync(startTimeInclusive, endTimeInclusive, maximumItems, token));
		}

		/// <summary>
		/// Asynchronously searches for appointments that occur between the specified start date and time and end date and time, 
		/// from the specified data source, returning no more than the specified number of appointments.
		/// </summary>
		/// <param name="appointments">The object used to acquire appointment data.</param>
		/// <param name="startTimeInclusive">The start date and time to use to search for appointments.</param>
		/// <param name="endTimeInclusive">The end date and time to use to search for appointments.</param>
		/// <param name="maximumItems">The maximum number of appointments to return.</param>
		/// <param name="account">The data source to search for appointments.</param>
		/// <returns>A singleton observable sequence containing the search results.</returns>
		public static IObservable<AppointmentsSearchEventArgs> SearchObservable(this Appointments appointments, DateTime startTimeInclusive, DateTime endTimeInclusive, int maximumItems, Account account)
		{
			Contract.Requires(appointments != null);
			Contract.Ensures(Contract.Result<IObservable<AppointmentsSearchEventArgs>>() != null);

			return SearchObservable(appointments, token => appointments.SearchAsync(startTimeInclusive, endTimeInclusive, maximumItems, account, token));
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
			Justification = "The Exception is passed to the subject.")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
			Justification = "AsyncSubject does not require disposal here.")]
		private static IObservable<AppointmentsSearchEventArgs> SearchObservable(Appointments appointments, Action<object> action)
		{
			Contract.Requires(appointments != null);
			Contract.Requires(action != null);
			Contract.Ensures(Contract.Result<IObservable<AppointmentsSearchEventArgs>>() != null);

			var token = new object();

			var subject = new AsyncSubject<AppointmentsSearchEventArgs>();

			var subscription = Observable.FromEventPattern<AppointmentsSearchEventArgs>(
				eh => appointments.SearchCompleted += eh,
				eh => appointments.SearchCompleted -= eh)
				.Where(e => e.EventArgs.State == token)
				.Take(1)
				.Select(e => e.EventArgs)
				.Subscribe(subject);

			try
			{
				action(token);
			}
			catch (Exception ex)
			{
				subscription.Dispose();

				subject.OnError(ex);
			}

			return subject.AsObservable();
		}
	}
}